package com.dlc.shop.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.bean.enums.SendType;
import com.dlc.shop.bean.model.NotifyTemplate;
import com.dlc.shop.bean.model.SmsLog;
import com.dlc.shop.bean.model.User;
import com.dlc.shop.common.bean.Sms4jConfig;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.Json;
import com.dlc.shop.common.util.RedisUtil;
import com.dlc.shop.config.ShopConfig;
import com.dlc.shop.dao.NotifyTemplateMapper;
import com.dlc.shop.dao.SmsLogMapper;
import com.dlc.shop.service.SmsLogService;
import com.dlc.shop.service.UserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.Map.Entry;


/**
 * @author lgh on 2018/11/29.
 */
@Service
@Slf4j
@AllArgsConstructor
public class SmsLogServiceImpl extends ServiceImpl<SmsLogMapper, SmsLog> implements SmsLogService {

    private SmsLogMapper smsLogMapper;
    private ShopConfig shopConfig;
    private final UserService userService;
    private final NotifyTemplateMapper notifyTemplateMapper;

    /**
     * 当天最大验证码短信发送量
     */
    private static final int TODAY_MAX_SEND_VALID_SMS_NUMBER = 10;

    /**
     * 一段时间内短信验证码的最大验证次数
     */
    private static final int TIMES_CHECK_VALID_CODE_NUM = 10;

    /**
     * 短信验证码的前缀
     */
    private static final String CHECK_VALID_CODE_NUM_PREFIX = "checkValidCodeNum_";

    /**
     * 短信验证码的前缀
     */
    private static final String CHECK_VALID_CODE_LOCK_PREFIX = "checkValidLock_";

    /**
     * 短信列表的大小/列表的索引
     */
    private static final Integer INDEX = 0;

    /**
     * 短信发送到达上限
     */
    private static final String SMS_MESSAGE_MAXIMUM = "number of short messages reached the maximum today";

    /**
     * 一分钟内重复发送
     */
    private static final String SMS_MESSAGE_REPEAT = "Text messages are sent too often";

    private RedissonClient redissonClient;

    @Override
    public void sendSms(SendType sendType, String userId, String mobile, LinkedHashMap<String, String> params) {
        // 非登录的验证码，非注册的验证码，非用户校验的验证码，都需要用户存在才行
        if (!Objects.equals(sendType,SendType.CAPTCHA)){
            User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUserMobile, mobile));
            if (Objects.isNull(user)) {
                // 用户不存在
                throw new YamiShopBindException("yami.user.account.no.exist");
            }
            if (user.getStatus() == 0){
                // 用户已禁用，请联系客服
                throw new YamiShopBindException("yami.user.disabled");
            }
        }
        RLock lock = redissonClient.getLock(CHECK_VALID_CODE_LOCK_PREFIX + mobile);
        try {
            lock.lock();
            // 校验并发送短信
            SmsLogService smsLogService = (SmsLogService)AopContext.currentProxy();
            Boolean flag = smsLogService.checkAndSendCode(sendType, userId, mobile, params);
            if(!flag){
                // 发送短信失败，请稍后再试
                throw new YamiShopBindException("yami.sending.message.failed");
            }
        } finally {
            lock.unlock();
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean checkAndSendCode(SendType sendType, String userId, String mobile, LinkedHashMap<String, String> params) {
        // 校验每日短信数量，以及1分钟内不可重复发送
        checkValidSendCount(sendType, userId, mobile, params);
        NotifyTemplate notifyTemplate = notifyTemplateMapper.selectOne(new LambdaQueryWrapper<NotifyTemplate>()
                .eq(NotifyTemplate::getSendType, sendType.getValue()));
        SmsLog smsLog = new SmsLog();
        smsLog.setType(sendType.getValue());
        smsLog.setMobileCode(params.get("code"));
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserId(userId);
        smsLog.setUserPhone(mobile);
        smsLog.setContent(formatContent(notifyTemplate.getMessage(), params));
        smsLogMapper.insert(smsLog);
        // 发送短信
        return this.sendSms(mobile, notifyTemplate.getTemplateCode(), params);
    }

    private void checkValidSendCount(SendType sendType, String userId, String mobile, LinkedHashMap<String, String> params) {
        boolean isValidCode = Objects.equals(SendType.CAPTCHA, sendType);
        // 验证码类短信需要检验发送次数
        if (!isValidCode) {
            return;
        }
//        // 阿里云短信规定：使用同一个签名，默认情况下对同一个手机号码发送验证码，最多支持1条/分钟，5条/小时，10条/天。
//        LambdaQueryWrapper<SmsLog> queryWrapper =  new LambdaQueryWrapper<SmsLog>();
//        queryWrapper.gt(SmsLog::getRecDate, DateUtil.beginOfDay(new Date()))
//                .lt(SmsLog::getRecDate, DateUtil.endOfDay(new Date()))
//                .orderByDesc(SmsLog::getRecDate);
//        if(Objects.isNull(userId)){
//            queryWrapper.isNull(SmsLog::getUserId);
//        }else{
//            queryWrapper.eq(SmsLog::getUserId, userId);
//        }
//        List<SmsLog> smsLogList = smsLogMapper.selectList(queryWrapper);
//        if (smsLogList.size() >= TODAY_MAX_SEND_VALID_SMS_NUMBER) {
//            // 今日发送短信验证码次数已达到上限
//            throw new YamiShopBindException("yami.verification.limit");
//        }
//
//        if (smsLogList.size() > INDEX){
//            SmsLog smsLogLast = smsLogList.get(INDEX);
//            long currentTimeMillis = System.currentTimeMillis();
//            long timeDb = DateUtil.offsetSecond(smsLogLast.getRecDate(), 60).getTime();
//            if (currentTimeMillis < timeDb){
//                // 一分钟内只能发送一次验证码
//                throw new YamiShopBindException("yami.verification.time.check");
//            }
//        }
        // 将上一条验证码失效
        smsLogMapper.invalidSmsByMobileAndType(mobile, sendType.getValue());
        String code = RandomUtil.randomNumbers(6);
        params.put("code", code);
    }



    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean checkValidCode(String mobile, String code, SendType sendType) {
        long checkValidCodeNum = RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
//        if (checkValidCodeNum == 0) {
        // 半小时后失效
        RedisUtil.expire(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1800);
//        }
        if (checkValidCodeNum >= TIMES_CHECK_VALID_CODE_NUM) {
            // 验证码校验过频繁，请稍后再试
            throw new YamiShopBindException("yami.user.code.check.too.much");
        }



        SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper<SmsLog>()
                .eq(SmsLog::getUserPhone, mobile)
                .eq(SmsLog::getMobileCode, code)
                .eq(SmsLog::getStatus, 1)
                .eq(SmsLog::getType, sendType.getValue()));
        // 没有找到当前的验证码
        if (dbSms == null) {
            RedisUtil.decr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return false;
        }
        RedisUtil.del(CHECK_VALID_CODE_NUM_PREFIX + mobile);
        // 标记为失效状态
        dbSms.setStatus(0);
        smsLogMapper.updateById(dbSms);
        // 验证码已过期
        DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 5);
        if (offsetMinute.getTime() < System.currentTimeMillis()) {
            RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return false;
        }

        return true;
    }

    @Override
    public Boolean sendMsgSms(String templateCode, String userMobile, LinkedHashMap<String, String> smsParam) {
        return sendSms(userMobile, templateCode, smsParam);
    }

    private Boolean sendSms(String mobile, String templateCode, LinkedHashMap<String, String> params) {
        Sms4jConfig sms4jConfig = shopConfig.getSmsConfig();
        try {
            SmsBlend smsBlend = SmsFactory.getSmsBlend(sms4jConfig.getSmsType());
            SmsResponse response = smsBlend.sendMessage(mobile, templateCode, params);
            log.info("短信发送返回结果：" + response.getData().toString());
            if (!response.isSuccess()) {
                throw new YamiShopBindException("发送短信失败:", response.getData().toString());
            }
            return true;
        } catch (SmsBlendException smsBlendException) {
            log.error("发送短信失败，请稍后再试：{}", smsBlendException.getMessage());
            if (smsBlendException.getMessage().contains(SMS_MESSAGE_MAXIMUM)) {
                throw new YamiShopBindException("短信发送到达上限");
            }
            if (smsBlendException.getMessage().contains(SMS_MESSAGE_REPEAT)) {
                throw new YamiShopBindException("一分钟内只能发送一次验证码");
            }
            return false;
        } catch (Exception e) {
            log.error("发送短信失败，请稍后再试：{}", e.getMessage());
            return false;
        }
    }

    private String formatContent(String content, LinkedHashMap<String, String> params) {
        for (Entry<String, String> element : params.entrySet()) {
            content = content.replace("${" + element.getKey() + "}", element.getValue());
        }
        return content;
    }
}
